package com.wlyuan.index.domain.source.assembler;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wlyuan.core.domain.valueobj.TenantId;
import com.wlyuan.core.domain.valueobj.UserId;
import com.wlyuan.core.domain.valueobj.VehicleLength;
import com.wlyuan.core.domain.valueobj.VehicleModel;
import com.wlyuan.core.marker.NamedUtils;
import com.wlyuan.core.marker.ValueUtils;
import com.wlyuan.core.named.TrackType;
import com.wlyuan.index.api.dto.waybill.WaybillArgumentsDTO;
import com.wlyuan.index.api.dto.waybill.WaybillIndexDTO;
import com.wlyuan.index.domain.source.Waybill;
import com.wlyuan.index.domain.source.utils.SourceUtils;
import com.wlyuan.index.domain.source.valueobj.Character;
import com.wlyuan.index.domain.source.valueobj.*;
import com.wlyuan.transport.dto.WaybillPriceDetailDTO;
import com.wlyuan.transport.dto.common.LocationDTO;
import com.wlyuan.transport.dto.common.WaybillCacheDTO;
import com.wlyuan.transport.dto.query.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.var;
import org.apache.commons.collections4.MapUtils;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.util.ArrayList;
import java.util.List;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class WaybillAssembler {
    @Mapper(uses = {ValueUtils.class, NamedUtils.class, SourceUtils.class})
    public interface Assembler {

        @Mappings({
                @Mapping(target = "device", ignore = true),
                @Mapping(target = "fromAddress", source = "fromAddress"),
                @Mapping(target = "toAddress", source = "toAddress"),
                @Mapping(target = "vehicle", source = "source.arguments"),
                @Mapping(target = "balance", source = "source.finance"),
                @Mapping(target = "affiliated", source = "source.followWaybill"),
                @Mapping(target = "original", source = "source.isRootWaybill"),
                @Mapping(target = "endBill", source = "source.billDayStatus"),
                @Mapping(target = "statistics", source = "source.waybillCache"),
                @Mapping(target = "trackType", source = "source.device.trackType"),
                @Mapping(target = "trackNo", source = "source.device.deviceCode"),
                @Mapping(target = "createBy", source = "source.createBy.userId"),
                @Mapping(target = "close.tenantId", source = "source.close.closeTenantId"),
                @Mapping(target = "close.status", source = "source.close.status"),
                @Mapping(target = "close.type", source = "source.close.type"),
                @Mapping(target = "close.reason", source = "source.close.reason"),
                @Mapping(target = "transportMode", source = "source.arguments.transportMode"),
                @Mapping(target = "distanceType", source = "source.arguments.distanceType"),
                @Mapping(target = "bookingEnabled", source = "source.showPickCovenant"),
                @Mapping(target = "booking.status", source = "source.pickCovenant.pickCovenantStatus"),
                @Mapping(target = "booking.arrivalTime", source = "source.pickCovenant.arrivalTime"),
        })
        Waybill assemble(WaybillIndexDTO source,
                         Address fromAddress,
                         Address toAddress,
                         List<Waybill.Relation> relations);

        Waybill.Device assembleDevice(DeviceDetailDTO source);

        @Mapping(target = "type", source = "source.signType")
        @Mapping(target = "quantity", source = "source.totalQuantity")
        Waybill.Sign assembleSign(WaybillSignDetailDTO source, TenantId tenantId);

        @Mapping(target = "type", source = "source.receiptType")
        @Mapping(target = "quantity", source = "source.totalQuantity")
        Waybill.Receipt assembleReceipt(WaybillReceiptDetailDTO source, TenantId tenantId);

        @Mapping(target = "stopQuote", source = "source.closeQuote")
//        @Mapping(target = "receiptPre", source = "source.receiptAttachmentStatus", defaultValue = "false")
//        @Mapping(target = "receiptLost", source = "source.receiptLoseStatus", defaultValue = "false")
        Waybill.Relation assembleRelation(WaybillRelationEventDTO source, Integer character);

        @Mappings({
                @Mapping(source = "combineContractNo", target = "contractNo")
        })
        Waybill.Combine assembleCombine(WaybillIndexDTO waybill);

        @Mapping(target = "name", source = "customerName")
        @Mapping(target = "type", source = "customerType")
        @Mapping(target = "highName", source = "showCustomerName")
        @Mapping(target = "highType", source = "showCustomerType")
        @Mapping(target = "tenantId", source = "customerTenantId")
        Customer assembleCustomer(WaybillCustomerDTO source);

        @Mapping(target = "name", source = "carrierName")
        @Mapping(target = "type", source = "carrierType")
        @Mapping(target = "tenantId", source = "carrierTenantId")
        @Mapping(target = "contactMobile", source = "carrierContactMobile")
        @Mapping(target = "contactName", source = "carrierContactName")
        @Mapping(target = "abbr", source = "carrierAbbr")
        Carrier assembleCarrier(WaybillCarrierDTO source);

        @Mapping(target = "name", source = "consigneeName")
        Consignee assembleConsignee(WaybillConsigneeDTO source);

        Address assembleAddress(LocationDTO source, WaybillContact contact);

        @Mapping(target = "name", source = "driverName")
        @Mapping(target = "mobile", source = "driverMobile")
        @Mapping(target = "plateNo", source = "vehiclePlateNo")
        Waybill.Driver assembleDriver(WaybillCarrierDTO source);

        @Mapping(target = "minimumFee", source = "source.minCost")
        Quote assembleQuote(WaybillPriceDetailDTO source, Integer transportMode);

        @Mapping(target = "differenceValue", source = "valueAddition")
        @Mapping(target = "differenceWeight", source = "weightAddition")
        @Mapping(target = "differenceVolume", source = "volumeAddition")
        Statistics assembleStatistics(WaybillCacheDTO source);


        @Mapping(target = "status", source = "financeFlag")
        @Mapping(target = "prepayFee", source = "prepayFeeFlag")
        @Mapping(target = "arrivePayFee", source = "arrivePayFeeFlag")
        @Mapping(target = "collectionFee", source = "collectionFeeFlag")
        @Mapping(target = "paymentFee", source = "waybillFeeFlag")
        Balance assembleBalance(WaybillFinanceDTO source);

        default Waybill.Vehicle assembleVehicle(WaybillArgumentsDTO source) {
            if (null == source.getVehicleLength() || null == source.getVehicleModel()) {
                return null;
            }
            var quantity = source.getVehicleQuantity();
            var length = new VehicleLength(null, source.getVehicleLength(),
                    source.getVehicleVolume(), source.getVehicleWeight());
            var model = new VehicleModel(source.getVehicleModel(), null, null);
            return new Waybill.Vehicle(model, length, quantity);
        }

        @Mapping(target = "id", source = "userId")
        UserId assembleUserId(UserDTO source);
    }

    private static final Assembler ASSEMBLER = Mappers.getMapper(Assembler.class);

    private static void addCombine(Waybill waybill, WaybillIndexDTO source) {
        if (null == source.getCombineId()) {
            return;
        }

        var combine = ASSEMBLER.assembleCombine(source);
        waybill.addCombine(combine);
    }

    private static void addSign(Waybill waybill, WaybillRelationEventDTO relation) {
        if (null == relation) {
            return;
        }

        var signMap = (JSONObject) JSON.toJSON(relation.getSign());
        if (MapUtils.isEmpty(signMap)) {
            return;
        }

        var sign = ASSEMBLER.assembleSign(relation.getSign(), new TenantId(relation.getTenantId()));
        waybill.addSign(sign);
    }

    private static void addReceipt(Waybill waybill, WaybillRelationEventDTO relation) {
        if (null == relation) {
            return;
        }
        var receiptMap = (JSONObject) JSON.toJSON(relation.getReceipt());
        if (MapUtils.isEmpty(receiptMap)) {
            return;
        }

        var receipt = ASSEMBLER.assembleReceipt(relation.getReceipt(), new TenantId(relation.getTenantId()));
        waybill.addReceipt(receipt);
    }

    private static void addDevice(Waybill waybill, WaybillIndexDTO source) {
        if (null == source.getDevice() || !TrackType.isDevice(source.getDevice().getTrackType())) {
            return;
        }
        waybill.addDevice(ASSEMBLER.assembleDevice(source.getDevice()));
    }

    private static void addDriver(Waybill waybill, WaybillIndexDTO source) {
        if (null == source.getCarrier() || null == source.getCarrier().getDriverId()) {
            return;
        }
        waybill.addDriver(ASSEMBLER.assembleDriver(source.getCarrier()));
    }

    private static boolean hasQuote(WaybillIndexDTO source) {
        if (null == source.getWaybillPrice()) {
            return false;
        }

        var price = source.getWaybillPrice();
        return null != price.getQuotationStatus() && 0 != price.getQuotationStatus();
    }

    private static void addQuote(Waybill waybill, WaybillIndexDTO source) {
        if (!hasQuote(source)) {
            return;
        }

        var arguments = source.getArguments();
        Integer transportMode = null;
        if (null != arguments) {
            transportMode = arguments.getTransportMode();
        }

        var quote = ASSEMBLER.assembleQuote(source.getWaybillPrice(), transportMode);
        waybill.addQuote(quote);
    }

    private static void addRelation(List<Waybill.Relation> relations, WaybillRelationEventDTO source, Character character) {
        var relation = ASSEMBLER.assembleRelation(source, character.getValue());
        relation.addHighlight(Highlight.SIGN_EXCEPTION, source.getSignExceptionStatus());
        relation.addHighlight(Highlight.RECEIPT_PHOTO, source.getReceiptAttachmentStatus());
        relation.addHighlight(Highlight.RECEIPT_LOST, source.getReceiptLoseStatus());
        relation.addHighlight(Highlight.RECEIPT_RECOVERED, source.getReceiptReplenishStatus());
        relation.addHighlight(Highlight.RECEIPT_REVIEWED, source.getReceiptReviewStatus());
        relations.add(relation);
    }

    public static Waybill assemble(WaybillIndexDTO source) {
        var relations = new ArrayList<Waybill.Relation>();

        if (null != source.getUpstream()) {
            addRelation(relations, source.getUpstream(), Character.CUSTOMER);
        }
        if (null != source.getDownstream()) {
            addRelation(relations, source.getDownstream(), Character.CARRIER);
        }

        Address fromAddress = null;
        if (null != source.getFromAddress()) {
            fromAddress = ASSEMBLER.assembleAddress(source.getFromAddress(), source.getFromContact());
        }

        Address toAddress = null;
        if (null != source.getToAddress()) {
            toAddress = ASSEMBLER.assembleAddress(source.getToAddress(), source.getToContact());
        }

        var waybill = ASSEMBLER.assemble(source, fromAddress, toAddress, relations);

        addDriver(waybill, source);
        addDevice(waybill, source);
        addCombine(waybill, source);
        addQuote(waybill, source);

        // 签收和回单
        addSign(waybill, source.getUpstream());
        addReceipt(waybill, source.getUpstream());
        addSign(waybill, source.getDownstream());
        addReceipt(waybill, source.getDownstream());
        return waybill;
    }
}
